package org.unidata.mdm.data.service.segments.records.merge;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;

import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.unidata.mdm.core.type.data.extended.WinnerInformationArrayAttribute;
import org.unidata.mdm.core.type.data.extended.WinnerInformationCodeAttribute;
import org.unidata.mdm.core.type.data.extended.WinnerInformationComplexAttribute;
import org.unidata.mdm.core.type.data.extended.WinnerInformationSimpleAttribute;
import org.unidata.mdm.core.type.timeline.TimeInterval;
import org.unidata.mdm.core.type.timeline.Timeline;
import org.unidata.mdm.core.util.SecurityUtils;
import org.unidata.mdm.data.context.PreviewRequestContext;
import org.unidata.mdm.data.dto.GetRecordDTO;
import org.unidata.mdm.data.module.DataModule;
import org.unidata.mdm.data.service.impl.RecordComposerComponent;
import org.unidata.mdm.data.type.data.EtalonRecord;
import org.unidata.mdm.data.type.data.OriginRecord;
import org.unidata.mdm.data.type.keys.RecordKeys;
import org.unidata.mdm.data.type.keys.RecordModificationBoxKey;
import org.unidata.mdm.system.type.pipeline.Finish;
import org.unidata.mdm.system.type.pipeline.Start;
import org.unidata.mdm.system.type.runtime.MeasurementPoint;

/**
 * @author Mikhail Mikhailov on May 11, 2020
 */
@Component(RecordPreviewFinishExecutor.SEGMENT_ID)
public class RecordPreviewFinishExecutor extends Finish<PreviewRequestContext, GetRecordDTO> {
    /**
     * This segment ID.
     */
    public static final String SEGMENT_ID = DataModule.MODULE_ID + "[RECORD_PREVIEW_FINISH]";
    /**
     * Localized message code.
     */
    public static final String SEGMENT_DESCRIPTION = DataModule.MODULE_ID + ".record.preview.finish.description";
    /**
     * The composer.
     */
    @Autowired
    private RecordComposerComponent recordComposerComponent;
    /**
     * Constructor.
     * @param id
     * @param description
     */
    public RecordPreviewFinishExecutor() {
        super(SEGMENT_ID, SEGMENT_DESCRIPTION, GetRecordDTO.class);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public GetRecordDTO finish(PreviewRequestContext ctx) {

        MeasurementPoint.start();
        try {

            Date ts = ctx.timestamp();
            String user = SecurityUtils.getCurrentUserName();

            // 0. Merge attributes, which may have been supplied for the operation.
            Map<String, String> manualMergeAttributes = ctx.getAttributes();

            // 1. Merge duplicates timeline to master timeline and generate preview.
            RecordKeys keys = ctx.keys();
            RecordKeys originalKeys = ctx.currentTimeline().getKeys();
            List<RecordKeys> duplicateKeys = ctx.duplicateKeys();

            final Map<String, String> attributeWinnersMap = new HashMap<>();
            final Map<String, String> originEtalonKeyMap = new HashMap<>();

            // 2. Create source etalon to ext. ID map
            Stream.concat(Stream.of(originalKeys), duplicateKeys.stream())
                .forEach(k -> k.getSupplementaryKeys().forEach(ok -> originEtalonKeyMap.put(ok.toBoxKey(), k.getEtalonKey().getId())));

            // 3. Process etalon. Should already be calculated by points.
            Timeline<OriginRecord> next = ctx.nextTimeline();
            TimeInterval<OriginRecord> interval = next.first();
            EtalonRecord result = interval.getCalculationResult();

            GetRecordDTO retval = new GetRecordDTO();
            if (result != null) {

                result.getAttributeValues().forEach(attribute -> {

                    String boxKey = null;
                    if (attribute instanceof WinnerInformationSimpleAttribute) {
                        WinnerInformationSimpleAttribute<?> wisa = (WinnerInformationSimpleAttribute<?>) attribute;
                        boxKey = RecordModificationBoxKey.toRecordBoxKey(wisa.getWinnerSourceSystem(), wisa.getWinnerExternalId());
                    } else if (attribute instanceof WinnerInformationComplexAttribute) {
                        WinnerInformationComplexAttribute wica = (WinnerInformationComplexAttribute) attribute;
                        boxKey = RecordModificationBoxKey.toRecordBoxKey(wica.getWinnerSourceSystem(), wica.getWinnerExternalId());
                    } else if (attribute instanceof WinnerInformationArrayAttribute) {
                        WinnerInformationArrayAttribute<?> wiaa = (WinnerInformationArrayAttribute<?>) attribute;
                        boxKey = RecordModificationBoxKey.toRecordBoxKey(wiaa.getWinnerSourceSystem(), wiaa.getWinnerExternalId());
                    } else if (attribute instanceof WinnerInformationCodeAttribute) {
                        WinnerInformationCodeAttribute<?> wica = (WinnerInformationCodeAttribute<?>) attribute;
                        boxKey = RecordModificationBoxKey.toRecordBoxKey(wica.getWinnerSourceSystem(), wica.getWinnerExternalId());
                    }

                    if (Objects.isNull(boxKey)) {
                        return;
                    }

                    String winnerEtalonId = originEtalonKeyMap.get(boxKey);
                    attributeWinnersMap.put(attribute.getName(), winnerEtalonId);
                });

                if (MapUtils.isNotEmpty(manualMergeAttributes)) {

                    attributeWinnersMap.putAll(manualMergeAttributes);
                    interval.toList().forEach(v -> {

                        List<String> attributeNames = new ArrayList<>(v.getValue().getAttributeNames());
                        for (String attr : attributeNames) {
                            if (attributeWinnersMap.containsKey(attr)
                            && !StringUtils.equals(attributeWinnersMap.get(attr), originEtalonKeyMap.get(v.toBoxKey()))) {
                                v.getValue().removeAttributeRecursive(attr);
                            }
                        }
                    });

                    // executed only for merge preview
                    recordComposerComponent.toEtalon(keys, interval, ts, user, true);
                    result = interval.getCalculationResult();
                }

                retval.setEtalon(result);
                retval.setAttributeWinnerMap(attributeWinnersMap);
            }

            return retval;
        } finally {
            MeasurementPoint.stop();
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean supports(Start<?, ?> start) {
        return PreviewRequestContext.class.isAssignableFrom(start.getInputTypeClass());
    }
}
